﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Reflection;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;
using System.Windows.Forms;

namespace Ling.P2P.Provider
{
    public class TcpP2PClientEntry : TcpP2PEntry
    {
        public TcpP2PClientEntry()
            : base()
        { }

        /// <summary>
        /// 获取该实例的 P2PEntry 类型。
        /// </summary>
        public override P2PEntryType EntryType
        {
            get { return P2PEntryType.P2PClient; }
        }

        #region 数据接收处理

        /// <summary>
        /// 侦听程序收到数据时的处理。
        /// </summary>
        /// <param name="skt"></param>
        protected override void AcceptData(Socket skt)
        {
            byte[] data;
            switch (CurrentMessage.Value.MessageType)
            {
                case P2PConstants.MSG_SEND_FILE: // 文件接收做专门处理。
                    // 先获得文件名。
                    data = ReceiveKnownData(skt, sizeof(int)); // 接收文件名的长度。
                    int nlen = BitConverter.ToInt32(data, 0);
                    data = ReceiveKnownData(skt, nlen); // 然后接收文件名的内容。
                    if (data != null)
                    {
                        ReceiveingFileName = Encoding.UTF8.GetString(data);
                        // 接收到文件名后需要在UI界面上提示是否接受。这一步定义为事件。
                        ReceiveFileEventArgs e = new ReceiveFileEventArgs((IPEndPoint)skt.RemoteEndPoint);
                        e.FileName = ReceiveingFileName;
                        e.StatusCode = 0; // 先置为等待状态。
                        OnRequestFileTrans(e);
                        while (e.StatusCode == 0) // 等待接受后继续。
                        {
                            Thread.Sleep(500);
                        }
                        if (e.StatusCode == P2PConstants.SEND_ACCEPT)
                        {   // 已接受文件传输。
                            skt.Send(BitConverter.GetBytes(P2PConstants.SEND_ACCEPT)); // 告诉对方接受。
                            ReceiveingFileName = e.FileName;
                            // 接下来接收文件数据
                            ReceiveFile(skt);
                        }
                        else
                        {   // 否则向发送方回馈拒绝请求指令。
                            skt.Send(BitConverter.GetBytes(P2PConstants.SEND_REFUSE));
                        }
                    }
                    break;
                default: // 非文件传输的其它情况。
                    // 其它类型数据的处理，直接丢给外面的事件中去处理这样灵活性更强。
                    OnReceiveData(new ReceiveDataEventArgs((IPEndPoint)skt.RemoteEndPoint, skt, CurrentMessage));
                    break;
            }
        }

        /// <summary>
        /// 接收被分批传送的大文件。
        /// </summary>
        /// <param name="skt">含接收数据源的 Socket 对象。</param>
        /// <param name="transferCount">文件被分做几次传过来。</param>
        private void ReceiveBigFile(Socket skt, int transferCount)
        {
            int rlen = CurrentMessage.Value.EachLength;
            // 若同名文件存在则删除之。
            if (File.Exists(ReceiveingFileName))
                File.Delete(ReceiveingFileName);
            FileStream fs = File.Create(ReceiveingFileName); // 创建新文件。
            for (int i = 0; i < transferCount; ++i)
            {
                if (rlen > (CurrentMessage.Value.ContentLength - ReceivedBytes))
                    rlen = (int)(CurrentMessage.Value.ContentLength - ReceivedBytes);
                byte[] data = ReceiveKnownData(skt, rlen);
                fs.Write(data, 0, rlen);
            }
            fs.Flush();
            fs.Close();
            fs.Dispose();
            ReceiveFileEventArgs e = new ReceiveFileEventArgs((IPEndPoint)skt.RemoteEndPoint);
            e.FileName = ReceiveingFileName;
            e.StatusCode = P2PConstants.SEND_SUCCESS;
            OnReceiveFileComplete(e);
        }

        /// <summary>
        /// 接收文件。
        /// </summary>
        /// <param name="skt"></param>
        private void ReceiveFile(Socket skt)
        {
            if (ReceivedBytes >= CurrentMessage.Value.ContentLength) // 文件已接收完成时不再处理。
            {
                try
                {
                    ReceivedBytes = 0x0;
                    if (CurrentMessage.Value.ContentLength > CurrentMessage.Value.EachLength)
                    {   // 此时说明文件是分批次传送的大文件。
                        int transferCount = GetTransferCount(CurrentMessage.Value.EachLength, CurrentMessage.Value.ContentLength);
                        ReceiveBigFile(skt, transferCount);
                    }
                    else
                    {   // 无需分批次传送的小文件。
                        int rlen = (int)CurrentMessage.Value.ContentLength;
                        byte[] data = ReceiveKnownData(skt, rlen);
                        // 若同名文件存在则删除之。
                        if (File.Exists(ReceiveingFileName))
                            File.Delete(ReceiveingFileName);
                        FileStream fs = File.Create(ReceiveingFileName); // 创建新文件。
                        fs.Write(data, 0, data.Length);
                        fs.Flush();
                        fs.Close();
                        fs.Dispose();
                        ReceiveFileEventArgs e = new ReceiveFileEventArgs((IPEndPoint)skt.RemoteEndPoint);
                        e.FileName = ReceiveingFileName;
                        OnReceiveFileComplete(e);
                    }
                }
                catch
                {
                    ReceiveFileEventArgs e = new ReceiveFileEventArgs((IPEndPoint)skt.RemoteEndPoint);
                    e.FileName = ReceiveingFileName;
                    OnReceiveFileComplete(e);
                }
            }
            skt.Close();
            skt.Dispose();
        }

        #endregion

        #region 数据发送处理

        /// <summary>
        /// 消息发送成功后的后期处理。
        /// </summary>
        /// <param name="Message">已被发送成功的消息。</param>
        /// <param name="skt">消息发送到的 Socket 对象。</param>
        /// <param name="AfterCall">回调函数。</param>
        private void ProcessingMessage(P2PMessage Message, Socket skt, AfterSendMessageHandler AfterCall)
        {
            switch (Message.MessageType)
            {
                case P2PConstants.MSG_LOG_IN:  // 登录消息
                case P2PConstants.MSG_MAINTAIN_LOGIN: // 维护登录状态
                    if (AfterCall != null)
                        AfterCall(Message, skt);
                    P2PMessage? LMsg = ReceiveMessage(skt);
                    if (LMsg != null)
                    {
                        if (AfterCall != null)
                            AfterCall(LMsg.Value, skt);
                        if (LMsg.Value.MessageType == P2PConstants.MSG_LOG_OK)
                        {
                            // 登录成功后启动本地侦听。
                            if (!StopListen)
                                StopListen = true;
                            Thread.Sleep(1000); // 为了让上一次的侦听程序完全结束，一秒钟后再重新开始。
                            _LocalIpEndPoint = (IPEndPoint)skt.LocalEndPoint;
                            ListenReceive();
                        }
                    }
                    break;
                case P2PConstants.MSG_LOG_OUT:
                    StopListen = true;
                    if (AfterCall != null)
                        AfterCall(Message, skt);
                    skt.Close();
                    skt.Dispose();
                    break;
                default:
                    if (AfterCall != null)
                        AfterCall(Message, skt);
                    skt.Close();
                    skt.Dispose();
                    break;
            }
        }

        /// <summary>
        /// 向指定的目标主机发送消息。
        /// </summary>
        /// <param name="Message">要发送的消息。</param>
        /// <param name="TargetEndPoint">目标主机地址信息。</param>
        /// <param name="AfterCall">消息发送成功后的回调函数（在其中处理后续的操作）。</param>
        public void SendMessage(P2PMessage Message, IPEndPoint TargetEndPoint, AfterSendMessageHandler AfterCall)
        {
            Thread sThd = new Thread(new ThreadStart(delegate 
            {
                Socket skt = SendMessage(Message, TargetEndPoint);
                ProcessingMessage(Message, skt, AfterCall);
            }));
            sThd.Start();
        }

        /// <summary>
        /// 向指定的 Socket 对象发送消息。
        /// </summary>
        /// <param name="Message">要发送的消息。</param>
        /// <param name="skt">要将消息发送到该 Socket 对象。</param>
        /// <param name="AfterCall">消息发送成功后的回调函数（在其中处理后续的操作）。</param>
        public void SendMessage(P2PMessage Message, Socket skt, AfterSendMessageHandler AfterCall)
        {
            byte[] msg_data = P2PMessageConverter.GetBytes(Message);
            byte[] data = new byte[msg_data.Length + 1];
            data[0] = P2PConstants.HEADER_IS_MSG;
            int dIndex = 1;
            for (int i = 0; i < msg_data.Length; ++i)
            {
                data[dIndex] = msg_data[i];
                dIndex++;
            }
            SendData(data, skt, false);
            ProcessingMessage(Message, skt, AfterCall);
        }

        /// <summary>
        /// 向指定的 Socket 对象发送数据（发送数据前将先发送其长度）。
        /// </summary>
        /// <param name="data">要发送的数据。</param>
        /// <param name="skt">数据要发送到的目标socket对象。</param>
        /// <returns></returns>
        public int SendData(byte[] data, Socket skt)
        {
            SendData(BitConverter.GetBytes(data.Length), skt, false); // 先发送长度。
            return SendData(data, skt, false);
        }

        /// <summary>
        /// 向指定的目标主机发送文件。
        /// </summary>
        /// <param name="filePath">文件路径。</param>
        /// <param name="TargetEndPoint">目标主机地址。</param>
        /// <param name="EachLen">每次最多传送多少字节。</param>
        public void SendFile(string filePath, IPEndPoint TargetEndPoint, int EachLen)
        {
            Thread sfThd = new Thread(new ThreadStart(delegate 
            {
                try
                {
                    // 先获得文件大小。
                    FileInfo fi = new FileInfo(filePath);
                    long file_size = fi.Exists ? fi.Length : 0;
                    if (file_size > 0)
                    {   // 构建文件传送消息，并计算文件是否要分批传送。
                        P2PMessage Msg = new P2PMessage();
                        Msg.MessageType = P2PConstants.MSG_SEND_FILE;
                        Msg.ContentLength = file_size;
                        Msg.EachLength = EachLen;
                        Msg.SendSeveral = GetTransferCount(EachLen, file_size);
                        Socket skt = SendMessage(Msg, TargetEndPoint);
                        // 接下来发送文件名到目标主机。
                        byte[] data = Encoding.UTF8.GetBytes(Path.GetFileName(filePath));
                        int slen = SendData(data, skt); // 然后发送文件名。
                        // 接下来准备接收对方的回馈指令。
                        data = ReceiveKnownData(skt, sizeof(int));
                        int statusCode = BitConverter.ToInt32(data, 0);
                        if (statusCode == P2PConstants.SEND_ACCEPT)
                        {
                            //对方接爱传送请求，开始传送文件。
                            if (Msg.SendSeveral > 1)
                                SendBigFile(fi, Msg, skt);
                            else
                                SendSmallFile(fi, skt);
                        }
                        skt.Close();
                        skt.Dispose();
                    }
                }
                catch (Exception Ex)
                {
                    MessageBox.Show(Ex.Message, "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }));
            sfThd.Start();
        }

        /// <summary>
        /// 发送小文件。
        /// </summary>
        /// <param name="file">文件对象。</param>
        /// <param name="skt">要将文件发送到该 Socket。</param>
        private void SendSmallFile(FileInfo file, Socket skt)
        {
            try
            {
                byte[] data = new byte[file.Length];
                FileStream fs = file.Open(FileMode.Open);
                fs.Read(data, 0, data.Length);
                fs.Close();
                fs.Dispose();
                SendData(data, skt, true);
                // 发送完成后触发事件。
                ReceiveFileEventArgs e = new ReceiveFileEventArgs(LocalIpEndPoint);
                e.FileName = file.Name;
                e.StatusCode = P2PConstants.SEND_SUCCESS;
                OnSendFileComplete(e);
            }
            catch
            {
                ReceiveFileEventArgs e = new ReceiveFileEventArgs(LocalIpEndPoint);
                e.FileName = file.Name;
                e.StatusCode = P2PConstants.SEND_FAIL;
                OnSendFileComplete(e);
            }
        }

        /// <summary>
        /// 发送超大文件。
        /// </summary>
        /// <param name="file">文件对象。</param>
        /// <param name="sendMsg">发送文件时的消息（消息内包含数据发送必须的相关信息，所以这里需要它）。</param>
        /// <param name="skt">要将文件发送到该 Socket。</param>
        private void SendBigFile(FileInfo file, P2PMessage sendMsg, Socket skt)
        {
            try
            {
                int dLen = sendMsg.EachLength;
                FileStream fs = file.Open(FileMode.Open);
                long sendLen = 0;
                for (int i = 0; i < sendMsg.SendSeveral; ++i)
                {
                    if (dLen > (sendMsg.ContentLength - sendLen))
                        dLen = (int)(sendMsg.ContentLength - sendLen);
                    byte[] data = new byte[dLen];
                    fs.Read(data, 0, dLen);
                    int slen = SendData(data, skt, false);
                    sendLen += slen;
                    OnSendingFile(new ReceivingFileEventArgs(sendMsg.ContentLength, sendLen));
                }
                fs.Close();
                fs.Dispose();
                ReceiveFileEventArgs e = new ReceiveFileEventArgs(LocalIpEndPoint);
                e.FileName = file.Name;
                e.StatusCode = P2PConstants.SEND_SUCCESS;
                OnSendFileComplete(e);
            }
            catch
            {
                ReceiveFileEventArgs e = new ReceiveFileEventArgs(LocalIpEndPoint);
                e.FileName = file.Name;
                e.StatusCode = P2PConstants.SEND_FAIL;
                OnSendFileComplete(e);
            }
        }

        #endregion
    }
}
